package com.zh.webflux.config;

import com.zh.webflux.auth.UserInfo;
import com.zh.webflux.auth.WebUserDetailService;
import com.zh.webflux.common.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufMono;

import java.net.InetSocketAddress;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

import static com.zh.webflux.util.MyDataBufferUtil.toJsonByteBuffer;

/**
 * 认证和授权配置
 * 权限配置
 * 登录配置
 * web用户的账号密码需要和数据库比对
 * admin用户账号密码是写死的
 *
 * @author ZH
 * @date 19:16 2021/1/7
 */
@Slf4j
//@EnableGlobalMethodSecurity(prePostEnabled = true) //表示开启权限的方法注解
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {

    //定义不需要权限认证的uri
    static Set<String> permitURIs = Set.of("/auth/**", "/hello/**");

    /**
     * 将登录用户信息以json格式响应给client端
     */
    private final BiFunction<WebFilterExchange, Authentication, Mono<Void>> respondUserInfo = (exchange, authentication) -> {
        ServerHttpResponse response = exchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json");
        //将用户信息返回
        Object principal = authentication.getPrincipal();
        return response.writeWith(ByteBufMono.just(response.bufferFactory().wrap(toJsonByteBuffer(principal))));
    };

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        //管理用户登录配置
        UserDetails user = User
                .withUsername("admin")
                .password("YWRtaW4wMA==") // base64加密'admin00'
                .passwordEncoder(pw -> passwordEncoder().encode(pw))
                .roles("ADMIN")
                .build();

        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager =
                new UserDetailsRepositoryReactiveAuthenticationManager(new MapReactiveUserDetailsService(user));

        authenticationManager.setPasswordEncoder(passwordEncoder());

        AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(authenticationManager);
        authenticationFilter.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler());
        authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        authenticationFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/admin/login"));
        authenticationFilter.setServerAuthenticationConverter(new ServerFormLoginAuthenticationConverter());
        authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());

        http.addFilterBefore(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN);

        //web用户登录配置
        return http
                .authorizeExchange(exchanges -> {
                            log.debug("权限白名单>>> permitURIs = {}", permitURIs);
                            //首页接口、注册相关接口跳过认证
                            exchanges.pathMatchers(permitURIs.toArray(String[]::new)).permitAll();
                            exchanges.anyExchange().authenticated();
//                            exchanges.anyExchange().permitAll(); //方便开发时用
                        }
                )
                .formLogin(form -> form
                        .authenticationSuccessHandler(webAuthenticationSuccessHandler())
                        .authenticationFailureHandler(authenticationFailureHandler())
                )
//                .formLogin(withDefaults())
                //登出配置
                .logout(logout ->
                        logout
                                .logoutUrl("/logout")
                                .logoutSuccessHandler(logoutSuccessHandler())
                )
                .exceptionHandling((exceptionHandlingSpec) -> {
                    // customize how to request for authentication
                    exceptionHandlingSpec.authenticationEntryPoint(serverAuthenticationEntryPoint());
                })
                //关闭csrf安全
                .csrf(csrf -> csrf.disable())
                .build();
    }

    @Bean
    public ReactiveUserDetailsService userDetailsService() {
        return new WebUserDetailService(passwordEncoder());
    }

    /**
     * 创建登录成功处理器
     *
     * @return
     */
    @Bean
    public ServerAuthenticationSuccessHandler webAuthenticationSuccessHandler() {
        /*
         * 获取请求来自客户端host
         */
        final Function<WebFilterExchange, String> remoteHostFun = (exchange) -> {
            ServerHttpRequest request = exchange.getExchange().getRequest();
            InetSocketAddress address = request.getRemoteAddress();
            assert address != null;
            return address.getHostName();
        };

        return (exchange, authentication) -> {
            log.debug("Web端登录成功处理Handler....");
            //将用户信息返回
            UserInfo userInfo = (UserInfo) authentication.getPrincipal();
            Assert.notNull(userInfo, "登录用户信息不能为null");

            return respondUserInfo.apply(exchange, authentication);
        };
    }


    /**
     * 创建登录成功处理器
     *
     * @return
     */
    @Bean
    public ServerAuthenticationSuccessHandler adminAuthenticationSuccessHandler() {
        return (exchange, authentication) -> {
            log.debug("Admin端登录成功处理Handler....");
            return respondUserInfo.apply(exchange, authentication);
        };
    }


    /**
     * 登录失败处理
     *
     * @return
     */
    @Bean
    public ServerAuthenticationFailureHandler authenticationFailureHandler() {
        return (exchange, ex) -> {
            log.warn("登录失败处理Handler.... " + ex.getMessage());

            ServerHttpResponse response = exchange.getExchange().getResponse();
            response.setStatusCode(HttpStatus.BAD_REQUEST);
            response.getHeaders().add("Content-Type", "application/json");

            return response.writeWith(ByteBufMono.just(response.bufferFactory().wrap(toJsonByteBuffer(ApiResult.fail(ex.getMessage())))));
//            return response.setComplete();
        };
    }

    /**
     * 权限认证失败处理
     *
     * @return
     */
    @Bean
    ServerAuthenticationEntryPoint serverAuthenticationEntryPoint() {
        return (ServerWebExchange exchange, AuthenticationException ex) -> {
            log.warn("认证失败处理Handler.... " + ex.getMessage());

            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);

            response.getHeaders().add("Content-Type", "application/json");

            return response.writeWith(ByteBufMono.just(response.bufferFactory().wrap(toJsonByteBuffer(ApiResult.fail(ex.getMessage())))));
//            return response.setComplete();
        };
    }

    /**
     * 用户登出成功处理
     */
    @Bean
    public ServerLogoutSuccessHandler logoutSuccessHandler() {
        return (exchange, authentication) -> {
            log.debug("退出登录成功处理Handler....");

            ServerHttpResponse response = exchange.getExchange().getResponse();
            response.setStatusCode(HttpStatus.OK);
            return response.setComplete();
        };
    }

    /**
     * 密码加密器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
